/*
 * Shell Plus
 */

#include "shellpp.h"

/*
 * Global variables
 */
int exitSignal; ///< An int exit signal variable	
int lastReturn; ///< An int lastReturn variable 
FLAG cmdFlags; ///< Flag declaration (int)
/** Files variable*/
FILE *variableFile, *tempFile; ///< temporary file
char fileline[100]; ///< line length in files

/*
* Function declaration
*/
int cmdReader (char *);
int cmdExec (int, char**);
int cmdPipeExec (int, char**, int, char**);
int cmdRedirExec (int, char**, char*, char*);

/**
@brief Program main function with calling arguments
\param argc not used here
\param argv not used here   
*/
int main(int argc, char** argv)
{
    char *pcmdLine;
    char *pcwd;
    char prompt[PATH_MAX];

    while (exitSignal == 0)
    {
        pcwd = getcwd (NULL, PATH_MAX); ///< get curent working directory
        strcpy (prompt, pcwd);
        strcat (prompt, "$ ");

        // TODO accept all signals
        pcmdLine = readline (prompt); // display prompt & read line from input
        // TODO block all signals

        if (pcmdLine != NULL && strlen (pcmdLine) > 0)
        {
            add_history (pcmdLine);
            cmdReader (pcmdLine);    // interpret line
        }

        // TODO add check and notification of process termination (jobs management)

        // clean up
        free (pcwd);        // free cwd pointer (getcwd uses malloc)
        free (pcmdLine);    // free pointer to readline (readline uses malloc)
        // --------
    }

    printf ("Bye Bye!\n");

    return (EXIT_SUCCESS);
}

/**
 * command line reader and interpreter
 \param pcmdLine Pointer to char to be used
 */
int cmdReader (char *pcmdLine)
{
    int wordCount = 0, argcount = 0, argcount2 = 0, i,wordLength = 0;
    char *pwordTok;
    char **pplineWords = (char**)malloc (MAX_WORDS * sizeof (char *));
    char **argvector = (char**)malloc (MAX_ARGS * sizeof (char *));
    char **argvector2 = (char**)malloc (MAX_ARGS * sizeof (char *));
    char *redirectFileIn = (char*)malloc (MAX_FILENAME * sizeof (char *));
    char *redirectFileOut = (char*)malloc (MAX_FILENAME * sizeof (char *));
 
    FILE *scriptFile; 
    char scriptline[100];
    char *pch;
 

    pwordTok = strtok (pcmdLine, WORD_DELIM);

    if (pwordTok == NULL)
        return 1; // command line is empty or strtok faild

    while (pwordTok != NULL && wordCount <= MAX_WORDS)
    {
        pplineWords[wordCount] = (char*)malloc (sizeof (pwordTok));
        strcpy (pplineWords[wordCount], pwordTok);
        pwordTok = strtok (NULL, WORD_DELIM);
        wordCount++;
    }

    for (i = 0; i < wordCount; i++)
    {   
        //If word ends in .sh then it's a script file that needs to be read
        if (pch = strstr(pcmdLine,".sh"))
        {
            if(pcmdLine[strlen(pcmdLine)-3] == pch[0])
            {
                if((scriptFile = fopen(pcmdLine, "r+"))!=NULL)
                {
                    //run the command given by the scriptline2
                    while(fgets(scriptline, 200, scriptFile)!=NULL)
                    {
                        if(scriptline[0]!='#')
                        {
                            scriptline[(strlen(scriptline)-1)] = '\0';
                            cmdReader (scriptline);    // interpret line in script
                        }
                    }

                    fclose(scriptFile);
                }
                else fprintf (stderr, "Script file not existent\n");
                    return EXIT_FAILURE;
            }
        }
	// execute more commands (one after the other)
        else if (strcmp(pplineWords[i], ";") == 0)
        {
            if ((cmdFlags & F_STDIN) != 0 && (cmdFlags & F_STDOUT) != 0)
            {
                cmdRedirExec(argcount, argvector, redirectFileIn, redirectFileOut);
            }
            else
            {
                cmdExec (argcount, argvector);
            }
            argcount = 0;
        }
        // execute the next commnad if the first returns succesfull
        else if (strcmp(pplineWords[i], "&&") == 0)
        {
            cmdFlags |= F_RUNAND;
            
            if ((cmdFlags & F_STDIN) != 0 && (cmdFlags & F_STDOUT) != 0)
            {
                cmdRedirExec(argcount, argvector, redirectFileIn, redirectFileOut);
            }
            else
            {
                lastReturn = cmdExec (argcount, argvector);
            }
            argcount = 0;
        }
        // execute the next command if the first returns failure
        else if (strcmp(pplineWords[i], "||") == 0)
        {
            cmdFlags |= F_RUNOR;
            
            if ((cmdFlags & F_STDIN) != 0 && (cmdFlags & F_STDOUT) != 0)
            {
                cmdRedirExec(argcount, argvector, redirectFileIn, redirectFileOut);
            }
            else
            {
                lastReturn = cmdExec (argcount, argvector);
            }
            argcount = 0;
        }
        // mark flag for background process
        else if (strcmp(pplineWords[i], "&") == 0)
        {
            cmdFlags |= F_RUNBG; // run in bg (will be used in cmdExec - waitpid)
        }
        // pipe 2 commands
        else if (strcmp(pplineWords[i], "|") == 0)
        {
            if ((cmdFlags & F_PIPE) != 0)
            {
                fprintf (stderr, "Second level pipe not supported\n");
                argcount = argcount2 = 0;
                break;
            }

            if ((cmdFlags & F_STDIN) != 0 && (cmdFlags & F_STDOUT) != 0)
            {
                fprintf (stderr, "Redirection not supported when usign pipe\n");
                argcount = argcount2 = 0;
                break;
            }

            cmdFlags |= F_PIPE;

            // construct the second command argv until the end of cmd line or one of the flags
            while (++i < wordCount && strcmp(pplineWords[i], ";") != 0
                                 && strcmp(pplineWords[i], "&&") != 0
                                 && strcmp(pplineWords[i], "||") != 0
                                 && strcmp(pplineWords[i], "&") != 0
                                 && strcmp(pplineWords[i], "|") != 0)
            {
                argvector2[argcount2++] = pplineWords[i];
            }

            i--; // decrease the variable so that we get the flag in the next for loop

            cmdPipeExec (argcount, argvector, argcount2, argvector2);
            argcount = argcount2 = 0;
        }
        // mark flag and store input file for command input
        else if (strcmp(pplineWords[i], "<") == 0)
        {
            if ((cmdFlags & F_STDIN) != 0)
            {
                fprintf (stderr, "Second level redirection for \'<\' not supported\n");
                argcount = 0;
                break;
            }

            cmdFlags |= F_STDIN;

            if (++i < wordCount)
            {
                strcpy (redirectFileIn, pplineWords[i]);
            }
        }
        // mark flag and store output file for command output
        else if (strcmp(pplineWords[i], ">") == 0)
        {
            if ((cmdFlags & F_STDOUT) != 0)
            {
                fprintf (stderr, "Second level redirection for \'>\' not supported\n");
                argcount = 0;
                break;
            }

            cmdFlags |= F_STDOUT;

            if (++i < wordCount)
            {
                strcpy (redirectFileOut, pplineWords[i]);
            }
        }
        // construct argument vector for command
        else
        {   
            argvector[argcount++] = pplineWords[i];
        }
    }

    // run last command stored in argument vector
    if ((cmdFlags & F_STDIN) != 0 || (cmdFlags & F_STDOUT) != 0)
    {
        cmdRedirExec(argcount, argvector, redirectFileIn, redirectFileOut);
    }
    else
    { 
        cmdExec (argcount, argvector);
    }
      
    // clean up
    free (argvector);
    free (argvector2);
    free (redirectFileIn);
    free (redirectFileOut);
    for (; wordCount > 0; )
    {
        free (pplineWords[--wordCount]);
    }

    return EXIT_SUCCESS;
}

/**
 * command exe
 \param argc int with the number of arguments
 \param argv char vector with the arguments
 */
int cmdExec (int argc, char ** argv)
{
    char *varwordTok,tempString[100];
    int varFound=0;

    if (argc == 0)
    {
        // no command to run
        return EXIT_FAILURE;
    }

    // argc is incremented one too much in the cmdReader
    argv[argc] = NULL; // the function argv should have the last argument = NULL

    // AND OR
    if ((cmdFlags & F_RUNAND) != 0)
    {
        cmdFlags &= ~F_RUNAND;
        if (lastReturn != 0)
            return EXIT_SUCCESS;
    }
    else if ((cmdFlags & F_RUNOR) != 0)
    {
        cmdFlags &= ~F_RUNOR;
        if (lastReturn != 1)
            return EXIT_SUCCESS;
    }
    // -- END AND OR --

    // exit the shell
    if (strcmp (argv[0], "exit") == 0 || strcmp (argv[0], "e") == 0)
    {
        exitSignal = 1;
        return EXIT_SUCCESS;
    }

    // defvar internal command
    if (strcmp (argv[0], "defvar") == 0)
    {
        variableFile = fopen("shell_variables", "r+");
        tempFile = fopen("temp","w");
        varFound=0;

        while (fgets (fileline, 200, variableFile) != NULL)
        {
            strcpy (tempString, fileline);
            varwordTok = strtok (fileline, WORD_DELIM);

            if ( strcmp(varwordTok, argv[1])==0 )
            {
                varFound=1;
                fputs(argv[1], tempFile);
                fputs(" ", tempFile);
                fputs(argv[2], tempFile);
                fputs(" 0\n", tempFile);
            }
            else
            {
                fputs(tempString, tempFile);
            }
        }

        fputs(argv[1], tempFile);
        fputs(" ", tempFile);
        fputs(argv[2], tempFile);
        fputs(" 0\n", tempFile);
        
        if (varFound!=1)
        {
            fclose(tempFile);
            fclose(variableFile);
            remove("shell_variables");
            rename("temp", "shell_variables");
        }
        return EXIT_FAILURE;
    }
    // -----------------------------------------------------

    // -------------FOR TESTING ONLY------------------------
    if (strcmp (argv[0], "it") == 0)
    {
        for ( ; argc >= 0; argc--)
            printf ("argv[%d] = %s", argc, argv[argc]);
        return EXIT_SUCCESS;
    }

    if (strcmp (argv[0], "r0") == 0)
    {
        printf("just a test: return EXIT_SUCCESS\n");
        return EXIT_SUCCESS;
    }

    if (strcmp (argv[0], "r1") == 0)
    {
        printf("just a test: return EXIT_FAILURE\n");
        return EXIT_FAILURE;
    }
    // end testing

    int status;
    pid_t pid, waitPid;

    pid = fork();

    if (pid < 0)
    {
        // fork faild, report failure
        fprintf(stderr, "Fork faild");
        status = -1;
    }
    else if (pid == 0)
    {
        // child process
        execvp (argv[0], argv); // exec only returns on failure

        fprintf (stderr, "Exec faild, command did not run\n");
        _exit (EXIT_FAILURE);
    }
    else
    {
        // parrent process
        if ((cmdFlags & F_RUNBG) != 0)
        {
            cmdFlags &= ~F_RUNBG;
            waitPid = waitpid (pid, &status, WNOWAIT);  
        }
        else
        {
            waitPid = waitpid (pid, &status, 0);
        }

        if (waitPid != pid)
        {
            status = -1;
        }
    }
    
    return EXIT_SUCCESS;
}

/**
 * command pipe exe
 \param argc1 Nr of arguments for command one
 \param argv1 Vector of arguments for command one
 \param argc2 Nr of second arguments for command two
 \param argv2 Vector of second arguments for command two
 */
int cmdPipeExec (int argc1, char** argv1, int argc2, char** argv2)
{
    if (argc1 == 0 || argc2 == 0)
    {
        // no command to run for pipe
        return EXIT_FAILURE;
    }
    
    cmdFlags &= ~F_PIPE; // unset flag
	
    // argc is incremented one too much in the cmdReader
    argv1[argc1] = NULL; // the function argv should have the last argument = NULL
    argv2[argc2] = NULL;

    int fd[2];
    pid_t pid, pid2, waitPid;
    int status;

    if (pipe (fd) == -1)
    {
        fprintf (stderr, "Pipe failed.\n");
        return EXIT_FAILURE;
    }
    if ((pid = fork ()) < 0)
    {
        fprintf (stderr, "Fork failed.\n");
        return EXIT_FAILURE;
    }

    if (pid == 0)
    {
        // child process
        close(fd[1]);
        dup2(fd[0], 0);
        close(fd[0]);
        
        execvp(argv2[0], argv2);

        fprintf (stderr, "Pipe - cmd 2: Exec faild, command did not run\n");
        _exit (EXIT_FAILURE);
    }
    else
    {
        // parent process
        // need to fork again, so the shell isn't replaced
        if ((pid2 = fork ()) < 0)
        {
            fprintf (stderr, "Fork failed.\n");
            return EXIT_FAILURE;
        }

        if (pid2 == 0) {
            // child process
            close(fd[0]);
            dup2(fd[1], 1);
            close(fd[1]);

            execvp(argv1[0], argv1);

            fprintf (stderr, "Pipe - cmd 1: Exec faild, command did not run\n");
            _exit (EXIT_FAILURE);
        } 
        else
        {
            // parent process (the shell)
            close(fd[0]);
            close(fd[1]);
            waitPid = waitpid (pid, &status, 0);

            if (waitPid != pid)
            {
                status = -1;
            }
        }
    }

    return EXIT_SUCCESS;
}

/**
 * command exe for redirection
 \param argc Number of arguments
 \param argv Vector of arguments
 \param inFile Redirection input file
 \param outFile Redirection output file
 */
int cmdRedirExec (int argc, char** argv, char* inFile, char* outFile)
{
    if (argc == 0)
    {
        // no command to run
        return EXIT_FAILURE;
    }

    int status;
    pid_t pid, waitPid;

    pid = fork();

    if (pid < 0)
    {
        // fork faild, report failure
        fprintf(stderr, "Fork faild");
        status = -1;
    }
    else if (pid == 0)
    {
        // child process
        int inFileH, outFileH;

        // redirecting STDIN
        if ((cmdFlags & F_STDIN) != 0)
        {
            if ((inFileH = open (inFile, O_RDONLY, 0644)) == -1)
            {
                fprintf(stderr, "Open STDIN file faild\n");
                _exit (EXIT_FAILURE);
            }
            dup2 (inFileH, STDIN);
            close (inFileH);
        }

        // redirecting STDOUT
        if ((cmdFlags & F_STDOUT) != 0)
        {
            if ((outFileH = open (outFile, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1)
            {
                fprintf(stderr, "Open STDOUT file faild");
                _exit (EXIT_FAILURE);
            }

            dup2 (outFileH, STDOUT);
            close (outFileH);
        }

        execvp (argv[0], argv); // exec only returns on failure

        fprintf (stderr, "Redirect: Exec faild, command did not run\n");
        _exit (EXIT_FAILURE);
    }
    else
    {
        // parrent process

        waitPid = waitpid (pid, &status, 0);

        if (waitPid != pid)
        {
            status = -1;
        }
        
        // unset flags
        cmdFlags &= ~F_STDIN;
        cmdFlags &= ~F_STDOUT;
    }
    
    return EXIT_SUCCESS;
}
